/*
 *  copy.c
 *  
 *  CSE 3221 Assignment 1
 *

 */


#include <stdio.h>
#include <stdlib.h>
#include <wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <errno.h>
#include <string.h>
#include <time.h>

#define BSIZE 10	
typedef struct { /* byte read from the file */
	char byte;
	off_t offset;
} Item_t;

typedef struct { /* data shared by readers and writers */			  
	int nextIn;
	int nextOut;
	int readerCount;
	sem_t buffer_m, bufEmpty_m, bufFull_m;
	//sem_t wMutex, rMutex,nEmpty, nOccupied;  /* semaphores*/
	Item_t buf[];
} Buffer_t;


Item_t readFile(int fd, sem_t *readFile_m);
void writeFile(int fd, Item_t item, sem_t *writeFile_m);
void writeLogFile(int fd, const char* mutex_name, char* msg, sem_t *logFile_m);
int writeBuffer(Buffer_t *b, Item_t item, int bufSize) ;
int bufferEmpty(Buffer_t *b, int bufSize);
Item_t readBuffer(Buffer_t *b, int bufSize, int nReader, int *index);

int main(int argc, char* argv[])
{
	
	int nReader, nWriter, nProcess, bufSize;
	int fdSrc,fdDes, fdrLog, fdwLog; /* file descriptors */
	int i, status;
	int shm_id; /* shared memory segment id */
	Buffer_t *shm_ptr; /* stores the address of the shared memory segment */
	int sizeShm;  /* Total size of the shared memory segment */
	struct timespec sleepTime;
   	sleepTime.tv_nsec = 10000000;
	Item_t nextItem;
	
	pid_t pid[atoi(argv[1]) + atoi(argv[2])] ;
	pid_t pid_main;
	char log[10];
	//char *wLog, *rLog;  /* log file entry */	
	int bufIndex;  /* the index when written to the buffer */	
	sem_t *readFile_m, *writeFile_m, *readLog_m, *writeLog_m;
	
	/* temporary variables in assist with moving all functions into main */
	off_t offset; 
	int statusRead;   
	
	
	/* Checking for invalid command line arguments */
	if ( argc != 8 ) {
		fprintf(stderr,"usage: cpy <nReaders> <nWriters> <file> <copy> <bufSize> <readerLog> <writerLog>\n");
		exit(1);
	}
	if ( atoi(argv[1]) < 1 ) {
		perror("At least 1 reader required!\n");
		exit(1);
	}
	if (atoi(argv[2]) < 1 ) {
		perror("At least 1 writer required!\n");
		exit(1);
	}
	/* open files specified on the command line */
	if ( (fdSrc = open(argv[3], O_RDONLY)) < 0 ) {
		perror("Cannot open file\n ");
		exit(1);
	}
	if ( (fdDes = open(argv[4], O_CREAT|O_RDWR|O_TRUNC)) < 0) {
		fprintf(stderr,"Cannot open the desctination file %s \n", argv[4]);
		exit(1);
	}
	if ( (fdrLog = open(argv[6], O_CREAT|O_WRONLY|O_TRUNC)) < 0 ) {
		fprintf(stderr, "Cannot open the reader log file.\n");
		exit(1);
	}
	if ( (fdwLog = open(argv[7], O_CREAT|O_WRONLY|O_TRUNC)) < 0) {
		fprintf(stderr, "Cannot open the writer log file\n");
		exit(1);
	}
	
	/* process the commend line argments */
	nReader=atoi(argv[1]); 
	nWriter=atoi(argv[2]); 
	bufSize=atoi(argv[5]);	
	nProcess = atoi(argv[1]) + atoi(argv[2]);
	sizeShm = (int)(sizeof(Buffer_t)+bufSize*sizeof(Item_t));
	
	/* Initialize string buffer for the logs*/
	//wLog = malloc(10*sizeof(char));
	//rLog = malloc(10*sizeof(char));
	
	/* Root creates shared memory segment */
	shm_id = shmget(IPC_PRIVATE, sizeShm, SHM_R|SHM_W);
	if (shm_id < 0) {
		perror("shmget error. \n");
		exit(1);
	}
	
	/* Root attach the shared memory segment */
	shm_ptr = (Buffer_t *)shmat(shm_id, NULL,0);
	
	if (shm_ptr < 0) {
		perror("Buffer error.\n");
		exit(1);
	}
	
	/* Initialize the buffer*/
	for(i=0;i<bufSize;i++)
		shm_ptr->buf[i].byte='\0';
	
	/* Initialize the shared memory segment */
	shm_ptr->nextIn = 0;
	shm_ptr->nextOut = 0;
	shm_ptr->readerCount=0;
	
	/* Initialize the unnamed semaphores */
	sem_init(&shm_ptr->buffer_m,0,1);
	sem_init(&shm_ptr->bufEmpty_m, 0,bufSize);
	sem_init(&shm_ptr->bufFull_m, 0,0);
	
	/*Initialize the named semaphores(unamed and named)*/
	const char rf_semname[]="rfea_qea";
	const char wf_semname[]="wfea_qea";
	const char rl_semname[]="rlea_qea";
	const char wl_semname[]="wlea_qea";
	
	readFile_m=sem_open(rf_semname, O_CREAT, 0666,1);
	writeFile_m=sem_open(wf_semname, O_CREAT, 0666,1);
	readLog_m=sem_open(rl_semname, O_CREAT, 0666,1);
	writeLog_m=sem_open(wl_semname, O_CREAT, 0666,1);
	
	
	/* Create reader & writer processes */
	for ( i = 0; i < nProcess; i++) {
		if ((pid[i] = fork())  < 0 ) {
			perror("Failed to fork().\n");
			exit(1);
		} 
		else if (pid[i] == 0 ) {
			srand(getpid());  /* seed the random number generator */
			break;
		}
	}
	
	/* Seperates the reader and writer process into two different categroy  */
	/* Child processes processing */
	
	if ( i < atoi(argv[1]) ) {  /* reader process */
		/* Steps of read processes */
		/*
		 * 1. read from Source file
		 * 2. Write to the buffer
		 * 3. write to the log File
		 *
		 */
		
		do{			
			//nextItem = readFile(fdSrc, readFile_m); /* read from source file */
			/*  ====== Reader reads from source file ====== */
			if(sem_wait(readFile_m)==-1)
			{
				perror("readFile_m semaphore invalid");
				exit(1);
			}

			///*read file*///
			if ((offset = lseek(fdSrc, 0, SEEK_CUR)) == -1)  //sets the offset to the next
			{
				perror("lseek error"); exit(1);
			}	
				
			nextItem.offset = offset;
			statusRead = read(fdSrc, &nextItem.byte, 1);
			//When the file reaches EOF, offset indicates the result.
			if(statusRead==0)
			{
				nextItem.offset=-1;
			}	
			
			///*SEMAPHORE*///
			if(sem_post(readFile_m)==-1)
			{
				perror("readFile_m semaphore invalid");
				exit(1);
			}

			
			
			/*Checks the termination condition for the reader process*/
			if(nextItem.offset == -1)
			{
				sem_wait(&shm_ptr->buffer_m);
				shm_ptr->readerCount++; //increase the termination number of the reader
				if((shm_ptr->readerCount)==nReader)
				{
					sem_post(&shm_ptr->bufFull_m);
				}
				sem_post(&shm_ptr->buffer_m);
				exit(0);
			}
			
			/* ====== reader process starts to write to the buffer ===== */
			//bufIndex = writeBuffer(shm_ptr, nextItem, bufSize);	
			
			/* Critical Section */
			if(sem_wait(&shm_ptr->bufEmpty_m)==-1) //asks if the buffer is empty
			{
				perror("Reader:bufEmpty_m semaphore invalid");
			}			
			if(sem_wait(&shm_ptr->buffer_m)==-1)
			{
				perror("Reader:buffer_m semaphore invalid");
			}
			
			bufIndex = shm_ptr->nextIn;
			shm_ptr->buf[bufIndex] = nextItem;
			shm_ptr->nextIn++;
			shm_ptr->nextIn %= bufSize;
			
			if(sem_post(&shm_ptr->buffer_m)==-1)
			{
				perror("Reader:buffer_m semaphore invalid");
			}
			//the buffer is filled now, so bufFull++
			if(sem_post(&shm_ptr->bufFull_m)==-1) //buffer is now okay to be used by writer
			{
				perror("Reader:bufFull_m semaphore invalid");
			}							
			
			nanosleep(&sleepTime, NULL);
			
			
			/* ====== reader process starts to write to the log file =====  */
			sprintf(log, "%d %d %d\n", i,(int)nextItem.offset,bufIndex);
			//writeLogFile(fdrLog,rl_semname,wLog, readLog_m);  /* write to the read log file */
			
			/*Critical section*/
			//Semaphore//
			if(sem_wait(readLog_m)==-1)
			{
				perror("Log file semaphore invalid");
				exit(1);
			}
			
			//writes the log
			write(fdrLog, log,(int)strlen(log) );
			
			//Semaphore//	
			if(sem_post(readLog_m)==-1)
			{
				perror("Log file semaphore invalid");
				exit(1);
			}

			nanosleep(&sleepTime, NULL);
			
		}while(1);
		
		
		
	} else if ( i >= atoi(argv[1]) && i < nProcess ) { /* writer process */
		//		/* steps for writer process  
		//		 * 1. read from buffer
		//		 * 2. write to target file
		//		 * 3. write to log file
		//		 */
		//		
		
		int *w_bufIndex=(int *)malloc(sizeof(int));	
			
		do{
		
			/* ======  writer process starts to read from the buffer ===== */
			//nextItem = readBuffer(shm_ptr, bufSize, nReader, w_bufIndex);  /* read buffer */
			nanosleep(&sleepTime, NULL);
			
			/* Critical Section */
			if(sem_wait(&shm_ptr->bufFull_m)==-1) //is the buffer full?
			{
				perror("Writer:bufFull_m semaphore invalid");
			}
			
			if(sem_wait(&shm_ptr->buffer_m)==-1)
			{
				perror("Writer:buffer_m		semaphore invalid");
			}
			
			int bufEmtpy=bufferEmpty(shm_ptr, bufSize);	
			
			bufIndex = shm_ptr-> nextOut;
			nextItem = shm_ptr->buf[bufIndex];
			shm_ptr->buf[bufIndex].byte='\0';
			shm_ptr->buf[bufIndex].offset=0;	
			shm_ptr->nextOut++;
			shm_ptr->nextOut %= bufSize;
			
			if((bufEmtpy==1) && (shm_ptr->readerCount==nReader)) //bufferEmpty(b)=1 means, the buffer is emtpy
			{
				
				nextItem.byte=-1;		
			}
			
			if(sem_post(&shm_ptr->buffer_m)==-1)
			{
				perror("Writer:buffer_m semaphore invalid");
			}
			
			if(sem_post(&shm_ptr->bufEmpty_m)==-1) //a buffer has an emtpy spot
			{
				perror("Writer:bufEmpty_m semaphore invalid");
			}

			
			
			
			
			if(nextItem.byte==-1)
				break; //All the reader is finished and buffer is emtpy
			
			
			sem_wait(writeFile_m);
			//writeFile(fdDes, nextItem);  /* write to the target file */
			lseek(fdDes, nextItem.offset, SEEK_SET);
			write(fdDes,&nextItem.byte,1);
			sem_post(writeFile_m);
			nanosleep(&sleepTime, NULL);
			
			//Prepares the log
			
			sprintf(log, "%d %d %d\n", i,(int)nextItem.offset,*w_bufIndex);
			//writeLogFile(fdwLog,wl_semname, wLog, writeLog_m); /* write to the log file */
			nanosleep(&sleepTime, NULL);
		}while(1);
		sem_post(&shm_ptr->bufFull_m);
		exit(0);
		
		
	} else { /* root process  */

		/* wait for all children to exit */	
		
		while (nProcess > 0 ) {  
			pid_main = wait(&status);
			nProcess--;		
		}
		
		/* Root append 'DONE' to the end */
		lseek(fdDes, 0, SEEK_END);
		write(fdDes,&"DONE\n",5);
		
		/* close file descriptors  */
		close(fdSrc);
		close(fdDes);
		close(fdrLog);
		close(fdwLog);
		
		/*close unnamed semaphores*/
		sem_destroy(&shm_ptr->buffer_m);
		sem_destroy(&shm_ptr->bufFull_m);
		sem_destroy(&shm_ptr->bufEmpty_m);
		
		
		/*close unnamed semaphores*/
		sem_unlink(rf_semname);
		sem_unlink(wf_semname);
		sem_unlink(rl_semname);
		sem_unlink(wl_semname);
		sem_close(readFile_m);
		sem_close(writeFile_m);
		sem_close(readLog_m);
		sem_close(writeLog_m);
		
		/* Detach from the shared memory segment */
		shmdt(shm_ptr);
		
		/* remove the shared memory segment */
		shmctl(shm_id, IPC_RMID, NULL);
		
	}
	
	exit(0);
}

/* reader reads from source file */
Item_t readFile(int fd, sem_t *readFile_m)
{
	Item_t item;
	off_t offset;
	int statusRead;
	
	/* Critical Section */	
	///*SEMAPHORE*///
	if(sem_wait(readFile_m)==-1)
	{
		perror("readFile_m semaphore invalid");
		exit(1);
	}
	
	///*read file*///
	if ((offset = lseek(fd, 0, SEEK_CUR)) == -1)  //sets the offset to the next
	{
		perror("lseek error"); exit(1);
	}	
		
	item.offset = offset;
	statusRead = read(fd, &item.byte, 1);
	//When the file reaches EOF, offset indicates the result.
	if(statusRead==0)
	{
		item.offset=-1;
	}	
	
	///*SEMAPHORE*///
	if(sem_post(readFile_m)==-1)
	{
		perror("readFile_m semaphore invalid");
		exit(1);
	}
	
	return item;
}




/* Write to the target File */
void writeFile(int fd, Item_t item, sem_t *writeFile_m)
{
	
	/* Critical Section */
	
	/* write to the target file */
	//	if(write(fd,&item.byte,1))
	if(pwrite(fd, &item.byte, 1, item.offset))
	{
		perror("pwrite failed");
		//		exit(1);
	}
	
}


void writeLogFile(int fd, const char *mutex_name, char* msg, sem_t *logFile_m)
{
	/*Critical section*/
	//Semaphore//
	if(sem_wait(logFile_m)==-1)
	{
		perror("Log file semaphore invalid");
		exit(1);
	}
	
	//writes the log
	write(fd, msg,(int)strlen(msg) );
	
	//Semaphore//	
	if(sem_post(logFile_m)==-1)
	{
		perror("Log file semaphore invalid");
		exit(1);
	}
}




/* reader writes to the buffer */
int writeBuffer(Buffer_t *b, Item_t item, int bufSize) 
{
	int index;

	/* Critical Section */
	if(sem_wait(&b->bufEmpty_m)==-1) //asks if the buffer is empty
	{
		perror("Reader:bufEmpty_m semaphore invalid");
	}			
	if(sem_wait(&b->buffer_m)==-1)
	{
		perror("Reader:buffer_m semaphore invalid");
	}
	
	index = b->nextIn;
	b->buf[index] = item;
	b->nextIn++;
	b->nextIn %= bufSize;
	
	if(sem_post(&b->buffer_m)==-1)
	{
		perror("Reader:buffer_m semaphore invalid");
	}
	//the buffer is filled now, so bufFull++
	if(sem_post(&b->bufFull_m)==-1) //buffer is now okay to be used by writer
	{
		perror("Reader:bufFull_m semaphore invalid");
	}	
	return index;
}



int bufferEmpty(Buffer_t *b, int bufSize)
{
	int i;
	for(i=0; i< bufSize;i++)
	{
		if( b->buf[i].byte != '\0')
			return 0; // not emtpy
	}
	return 1;
}

Item_t readBuffer(Buffer_t *b, int bufSize, int nReader, int *index)
{
	Item_t item;
	//	sem_wait(&b->nOccupied);
	//	sem_wait(&b->rMutex);
	//	
	/* Critical Section */
	if(sem_wait(&b->bufFull_m)==-1) //is the buffer full?
	{
		perror("Writer:bufFull_m semaphore invalid");
	}
	
	if(sem_wait(&b->buffer_m)==-1)
	{
		perror("Writer:buffer_m		semaphore invalid");
	}
	int bufEmtpy=bufferEmpty(b, bufSize);	
	*index = b-> nextOut;
	item = b->buf[*index];
	b->buf[*index].byte='\0';
	b->buf[*index].offset=0;	
	b->nextOut++;
	b->nextOut %= bufSize;
	
	if((bufEmtpy==1) && (b->readerCount==nReader)) //bufferEmpty(b)=1 means, the buffer is emtpy
	{
		
		item.byte=-1;		
	}

	
	if(sem_post(&b->buffer_m)==-1)
	{
		perror("Writer:buffer_m semaphore invalid");
	}
	
	if(sem_post(&b->bufEmpty_m)==-1) //a buffer has an emtpy spot
	{
		perror("Writer:bufEmpty_m semaphore invalid");
	}
	
	return(item);
	
	
	
	
	
	
}


